#!/usr/bin/env bash

include "locker.sh"
include "objectv4.sh"
include "test.sh"

ObjectV4::DefineClass DatabaseV2 "
    --cargs=path
    --string=path
"

function DatabaseV2::DatabaseV2()
{
    local this="$1"; shift
    local path="$(DatabaseV2::path "$this")"
    if ! [ -e "$path" ]; then
        mkdir -p "$path"
    fi
}

function DatabaseV2::IsObject()
{
    local this="$1"; shift
    local path="$(DatabaseV2::path "$this")"
    while (( $# > 0 )); do
        path+="/$1"; shift
    done

    [ -d "$path" ]
}

function DatabaseV2::IsArray()
{
    local this="$1"; shift
    local path="$(DatabaseV2::path "$this")"
    while (( $# > 0 )); do
        path+="/$1"; shift
    done

    [ -f "$path/0" ]
}

function DatabaseV2::IsExist()
{
    local this="$1"; shift
    local path="$(DatabaseV2::path "$this")"
    while (( $# > 0 )); do
        path+="/$1"; shift
    done

    [ -e "$path" ]
}

function DatabaseV2::GetVal()
{
    if DatabaseV2::IsObject "$@"; then
        assert_fail "DatabaseV2::GetVal 无法获取对象的值: '$*'"
        return 1
    fi

    if ! DatabaseV2::IsExist "$@"; then
        assert_fail "DatabaseV2::GetVal 对象不存在: '$*'"
        return 1
    fi

    local this="$1"; shift
    local path="$(DatabaseV2::path "$this")"
    while (( $# > 0 )); do
        path+="/$1"; shift
    done

    cat "$path"
}

function DatabaseV2::SetVal()
{
    local this="$1"; shift
    local path="$(DatabaseV2::path "$this")"
    while (( $# > 2 )); do
        path+="/$1"; shift
    done

    local file="$1"; shift
    local content="$1"; shift
    mkdir -p "$path"
    if [ -d "$path/$file" ]; then
        assert_fail "DatabaseV2::SetVal 目标是一个对象: '$path/$file'"
        return 1
    fi

    FileLocker locker{"$path/$file"}
    echo "$content" > "$path/$file"
}

function DatabaseV2::Del()
{
    local this="$1"; shift
    local path="$(DatabaseV2::path "$this")"
    while (( $# > 0 )); do
        path+="/$1"; shift
    done

    if [ -e "$path" ]; then
        FileLocker locker{"$path"}
        rm -rf "$path"
    fi
}

function DatabaseV2::EraseKey()
{
    local this="$1"; shift
    local path="$(DatabaseV2::path "$this")"
    while (( $# > 1 )); do
        path+="/$1"; shift
    done
    local index="$1"; shift

    if [ -e "$path" ]; then
        FileLocker locker{"$path"}

        debuglog "remove $path/$index"
        rm -rf "$path/$index"
        local i j
        for (( i = index, j = i + 1; ; i++, j++ )); do
            if ! [ -e "$path/$j" ]; then
                debuglog "not exist $j"
                break
            fi

            debuglog "move $j $i"
            mv "$path/$j" "$path/$i"
        done
    fi
}

function DatabaseV2::EraseVal()
{
    local this="$1"; shift
    local path="$(DatabaseV2::path "$this")"
    while (( $# > 1 )); do
        path+="/$1"; shift
    done
    local value="$1"; shift

    if [ -e "$path" ]; then
        FileLocker locker{"$path"}

        local not_exist=false
        cd "$path"
        debuglog "$(ls)"
        for index in $(ls); do
            if [ "$(cat "$path/$index")" == "$value" ]; then
                not_exist=true
                break
            fi
        done
        cd - >/dev/null

        if $not_exist; then
            debuglog "not found"
            return 1
        fi

        debuglog "remove $path/$index"
        rm -rf "$path/$index"
        local i j
        for (( i = index, j = i + 1; ; i++, j++ )); do
            if ! [ -e "$path/$j" ]; then
                debuglog "not exist $j"
                break
            fi

            debuglog "move $j $i"
            mv "$path/$j" "$path/$i"
        done
    fi
}

function DatabaseV2::GetLen()
{
    local this="$1"; shift
    local keys=($(DatabaseV2::GetKeys "$this" "$@"))
    echo "${#keys[*]}"
}

function DatabaseV2::GetKeys()
{
    local this="$1"; shift
    local path="$(DatabaseV2::path "$this")"
    while (( $# > 0 )); do
        path+="/$1"; shift
    done
    ls "$path"
}

function DatabaseV2::Test1()
{
    local testdb="$(mktemp --dry-run "/tmp/databasev2.test1.XXXXXXXX")"
    DatabaseV2 db{"$testdb"}

    # set/get/del
    test::should_return_false DatabaseV2::IsExist db global_data docker_version
                              DatabaseV2::SetVal  db global_data docker_version "15"
    test::should_return_true  DatabaseV2::IsExist db global_data docker_version
    test::run_cmd             DatabaseV2::GetVal  db global_data docker_version
    test::should_be           "15"
                              DatabaseV2::Del     db global_data docker_version
    test::should_return_false DatabaseV2::IsExist db global_data docker_version

    # set/set/get
    test::should_return_false DatabaseV2::IsExist db updater new
                              DatabaseV2::SetVal  db updater new "true"
                              DatabaseV2::SetVal  db updater new "false"
    test::should_return_true  DatabaseV2::IsExist db updater new
    test::run_cmd             DatabaseV2::GetVal  db updater new
    test::should_be           "false"

    # len
    test::should_return_false DatabaseV2::IsExist db repos
                              DatabaseV2::SetVal  db repos 0 name m1
                              DatabaseV2::SetVal  db repos 0 path path/m1
    test::should_return_true  DatabaseV2::IsExist db repos
    test::should_return_true  DatabaseV2::IsExist db repos 0
    test::should_return_true  DatabaseV2::IsExist db repos 0 name
    test::should_return_true  DatabaseV2::IsExist db repos 0 path
    test::should_return_false DatabaseV2::IsExist db repos 1
    test::run_cmd             DatabaseV2::GetLen  db repos
    test::should_be           "1"
    test::run_cmd             DatabaseV2::GetKeys db repos
    test::should_be           "0"

                              DatabaseV2::destroy db
    rm -rf "$testdb"
}
test::register_test DatabaseV2::Test1

function DatabaseV2::Test2()
{
    local testdb="$(mktemp --dry-run "/tmp/databasev2.test2.XXXXXXXX")"
    DatabaseV2 db{"$testdb"}

    # pre
    test::should_return_false DatabaseV2::IsExist  db repos
                              DatabaseV2::SetVal   db repos 0 name m1
                              DatabaseV2::SetVal   db repos 0 path path/m1
                              DatabaseV2::SetVal   db repos 1 name m2
                              DatabaseV2::SetVal   db repos 1 path path/m2
                              DatabaseV2::SetVal   db repos 2 name m3
                              DatabaseV2::SetVal   db repos 2 path path/m3
    test::should_return_true  DatabaseV2::IsExist  db repos
    test::should_return_true  DatabaseV2::IsExist  db repos 2
    test::should_return_true  DatabaseV2::IsExist  db repos 2 name
    test::should_return_true  DatabaseV2::IsExist  db repos 2 path
    test::should_return_false DatabaseV2::IsExist  db repos 3
    test::run_cmd             DatabaseV2::GetVal   db repos 1 name
    test::should_be           "m2"
    test::run_cmd             DatabaseV2::GetVal   db repos 1 path
    test::should_be           "path/m2"
    test::run_cmd             DatabaseV2::GetLen   db repos
    test::should_be           "3"
    test::run_cmd             DatabaseV2::GetKeys  db repos
    test::should_be           "$(sed 's@ @\n@g' <<< "0 1 2")"

    # erase
                              DatabaseV2::EraseKey db repos 1
    test::should_return_true  DatabaseV2::IsExist  db repos
    test::should_return_true  DatabaseV2::IsExist  db repos 1
    test::should_return_true  DatabaseV2::IsExist  db repos 1 name
    test::should_return_true  DatabaseV2::IsExist  db repos 1 path
    test::should_return_false DatabaseV2::IsExist  db repos 2
    test::run_cmd             DatabaseV2::GetVal   db repos 1 name
    test::should_be           "m3"
    test::run_cmd             DatabaseV2::GetVal   db repos 1 path
    test::should_be           "path/m3"
    test::run_cmd             DatabaseV2::GetLen   db repos
    test::should_be           "2"
    test::run_cmd             DatabaseV2::GetKeys  db repos
    test::should_be           "$(sed 's@ @\n@g' <<< "0 1")"

                              DatabaseV2::destroy  db
    rm -rf "$testdb"
}
test::register_test DatabaseV2::Test2
